<script setup lang="ts">
import {ref, computed} from 'vue'
import {appThemeColors} from '@/config'
import {CloudUpload, Eye} from '@vicons/tabler'
import {CloudCheckmark20Filled} from '@vicons/fluent'
import {DeleteFilled} from '@vicons/antd'
import {NUpload, type UploadInst, type UploadFileInfo, useMessage} from 'naive-ui'
import {formatSize} from "@/utils";
import {postRequestWithHeaders, postRequest} from '@/api'
import {v4 as uuidv4} from 'uuid'

interface UploadFileResult {
  file: string;
  original_name: string
}

interface MergeChunks extends UploadFileResult {
  totalChunks?: number,
  group_id?: number,
  insert?: boolean,
}

const message = useMessage();

const fileSrc = defineModel<string | null | undefined>('fileSrc', {
  default: ''
})

const maskHover = ref<boolean>(false)
const preview = ref<boolean>(false)
const uploadRef = ref<UploadInst | null>(null)
//触发区域div
const triggerDiv = ref<HTMLElement | null>(null)
const chunkSize = 2 * 1024 * 1024;
const props = defineProps({
  ...NUpload.props,
  action: {
    type: String,
    default: '/sett/sub_up_file'
  },
  accept: {
    type: String,
    default: ''
  },
  fileName: {
    type: String,
    default: 'file'
  },
  pathName: {
    type: String,
    default: 'file'
  },
  type: {
    type: String,
    default: 'box'
  },
  width: {
    type: Number,
    default: 100
  },
  height: {
    type: Number,
    default: 100
  },
  text: {
    type: String,
    default: undefined
  },
  descriptions: {
    type: String,
    default: ''
  },
  fileSize: {
    type: Number,
    default: 2 //MB
  },
  groupId: {
    type: Number,
    default: 0
  },
  insert: {
    type: Boolean,
    default: false
  },
  chunked: {
    type: Boolean,
    default: false
  },
  drag: {
    type: Boolean,
    default: false
  },
  upLocal: {
    type: Boolean,
    default: true
  }
})

const uploadText = computed(() =>
    props.text !== undefined
        ? props.text
        : props.type === 'button'
            ? '上传文件'
            : '点击上传'
)

const hasImageAccept = computed(() =>
    /(\.png|\.jpg|\.jpeg|\.svg)/i.test(props.accept ?? '')
)


const fullFilePath = computed(() => fileSrc.value ? `/api/file/${fileSrc.value}` : '')

//当上传图片时有效
const onMouseEnter = () => {
  maskHover.value = true
}
const onMouseLeave = () => {
  maskHover.value = false
}

const delUpImg = () => {
  fileSrc.value = '';
  maskHover.value = false
}

//点击外按键触发上传
const click = () => {
  if (triggerDiv.value) {
    triggerDiv.value.click();
  }
}

//上传文件
const uploadFile = async (options: { file: UploadFileInfo, fileList: Array<UploadFileInfo>, event?: Event }) => {
  const file_info = options.file.file;
  if (!file_info) {
    return false;
  }
  const file_type = hasImageAccept.value ? '图片' : '文件'
  const file_size = file_info.size;
  if (file_size > (props.fileSize * 1024 * 1024)) {
    message.error(`上传的${file_type}过大，不得超过${formatSize(props.fileSize)}的${file_type}`)
    return false;
  }

  //直接上传
  if (!props.chunked) {
    const formData = new FormData()
    formData.append('up_local', props.upLocal.toString());
    formData.append(props.fileName, file_info)
    formData.append('path_name', props.pathName)
    const post_url=!props.chunked && props.action=='/sett/sub_up_file'?'/sett/upload_file':props.action
    await postRequestWithHeaders(post_url, formData, {'Content-Type': 'multipart/form-data'}).then((res: ResponseResult) => {
      if (res.code === 200) {
        const data = res.data as UploadFileResult;
        fileSrc.value = data.file;
      } else {
        message.error(`上传${file_type}失败！`)
      }
    })
    return;
  }
  //分块上传
  if (props.chunked) {
    const fileUuid = uuidv4(); // 生成 UUID
    const totalChunks = Math.ceil(file_size / chunkSize);
    const uploadPromises = [];
    let uploadFiles: MergeChunks = {
      file: '',
      original_name: '',
      totalChunks: 0,
    };

    for (let i = 0; i < totalChunks; i++) {
      const start = i * chunkSize;
      const end = Math.min(file_size, start + chunkSize);
      const chunk = file_info.slice(start, end);
      const formData = new FormData();
      formData.append('chunk', chunk);
      formData.append('chunkIndex', i.toString());
      formData.append('totalChunks', totalChunks.toString());
      formData.append('fileName', file_info.name);
      formData.append('fileUuid', fileUuid);
      formData.append('up_local', props.upLocal.toString());

      //遍历附带的参数
      for (const key in props?.data) {
        formData.append(`${key}`, `${props?.data[key]}`);
      }
      uploadPromises.push(postRequestWithHeaders(props.action, formData, {
        'Content-Type': 'multipart/form-data',
        'x-chunk-index': i.toString()
      }, false, i).then((res: ResponseResult) => {
        if (res.code == 200) {
          const data = res.data as UploadFileResult;
          uploadFiles = {original_name: data.original_name, file: data.file, totalChunks: totalChunks}
        }
      }))
    }
    try {
      await Promise.all(uploadPromises);
      await postRequest('/sett/merge_chunks', {
        original_name: uploadFiles.original_name,
        file_name: uploadFiles.file,
        totalChunks: uploadFiles.totalChunks,
        group_id: props.groupId,
        insert: props.insert,
        up_local: props.upLocal
      }).then((res: ResponseResult) => {
        if (res.code == 200) {
          fileSrc.value = uploadFiles.file;
        }
      })
    } catch (error) {
      console.error('上传失败', error);
    }
  }
}


defineExpose({click})
</script>

<template>
  <div :style="{'--drag-width':( props.drag && props.width <= 100 ? 300 : props.width)+'px','--drag-height':(props.drag && props.height <= 100 ? 160 : props.height)+'px','--primary-colors':appThemeColors.primaryColor}">
    <n-upload
        class="upload-file"
        ref="uploadRef"
        :accept="props.accept"
        :show-file-list="false"
        :default-upload="false"
        @change="uploadFile">
      <template v-if="props.drag">
        <n-upload-dragger class="custom-drag">
          <div class="not-upload" v-if="!fileSrc">
            <n-text style="font-size: 16px">
              点击或者拖动文件到该区域来上传
            </n-text>
            <n-p depth="3" style="margin: 8px 0 0 0;color: rgb(118, 124, 130)">
              请不要上传敏感数据，比如你的银行卡号和密码，信用卡号有效期和安全码
            </n-p>
          </div>
          <div v-else style="height: 100%;">
            <div v-if="hasImageAccept" class="has-background" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
              <img :src="fullFilePath" alt=""/>
              <div v-if="maskHover" class="has-background-mask">
                <n-button size="small" text  @click.stop="()=>(preview=true)">
                  <template #icon>
                    <n-icon size="22">
                      <Eye/>
                    </n-icon>
                  </template>
                </n-button>
                <n-button size="small" text  @click.stop="delUpImg">
                  <template #icon>
                    <n-icon size="22">
                      <DeleteFilled/>
                    </n-icon>
                  </template>
                </n-button>
              </div>
            </div>
            <div class="drag-uploaded-file" v-else>
              <n-icon size="48">
                <CloudCheckmark20Filled/>
              </n-icon>
              <n-text style="font-size: 20px;font-weight: bold">
                已上传
              </n-text>
            </div>
          </div>
        </n-upload-dragger>
      </template>
      <template v-else>
        <template v-if="props.type==undefined">
          <div ref="triggerDiv"></div>
        </template>
        <template v-else>
          <template v-if="props.type=='box' && hasImageAccept">
            <div class="select-area" :class="{ hasFile: fileSrc }"
                 :style="{width:props.width+'px',height:props.height+'px','--border-color':appThemeColors.primaryColor}">
              <div class="has-background" v-if="fileSrc" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
                <img :src="fullFilePath" alt=""/>
                <div v-if="maskHover" class="has-background-mask"
                     style="display: flex;align-items: center; justify-content: center;height: 100%;width: 100%;position: absolute;background: #d6d6d6d6;top: 0;gap: 12px;">
                  <n-button size="small" text  @click.stop="()=>(preview=true)">
                    <template #icon>
                      <n-icon size="22">
                        <Eye/>
                      </n-icon>
                    </template>
                  </n-button>
                  <n-button size="small" text  @click.stop="delUpImg">
                    <template #icon>
                      <n-icon size="22">
                        <DeleteFilled/>
                      </n-icon>
                    </template>
                  </n-button>
                </div>
              </div>
              <div v-else style="width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;">
                {{ uploadText }}
              </div>
            </div>
          </template>
          <template v-else-if="props.type=='button'">
            <n-button :type="fileSrc?'primary':'default'">
              <template #icon>
                <CloudCheckmark20Filled v-if="fileSrc"/>
                <CloudUpload v-else/>
              </template>
              {{ fileSrc ? '已上传' : uploadText }}
            </n-button>
          </template>
        </template>
      </template>
    </n-upload>

    <n-image-preview
        v-model:show="preview"
        :src="fullFilePath"
    />
  </div>
</template>

<style scoped lang="scss">
$border-radius: 3px;
.upload-file {
  width: var(--drag-width);
  height: var(--drag-height);
}

.custom-drag {
  padding: 0 !important;
  display: grid;
  align-items: center;
  justify-content: center;
  width: var(--drag-width);
  height: var(--drag-height);

  .not-upload {
    padding: 12px;
  }

  .drag-uploaded-file {
    display: grid;
    align-items: center;
    justify-content: center;
    color: var(--primary-colors);

    .n-text {
      color: var(--primary-colors);
    }
  }
}

.select-area {
  border: 1px dashed var(--upload-box-border-color);
  border-radius: $border-radius;
  cursor: pointer;
  background: var(--n-dragger-color);
  overflow: hidden;
  transition: background-color 0.3s ease,border-color 0.3s ease;
}

.select-area:hover {
  border-color: var(--border-color) !important;
}

.hasFile, .hasFile:hover {
  border: 1px solid rgb(224, 224, 230) !important;
}

.has-background {
  width: 100%;
  height: 100%;
  position: relative;

  img {
    width: 100%;
    height: 100%;
    border-radius: $border-radius;
  }

  &-mask {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 100%;
    position: absolute;
    background: #d6d6d6d6;
    top: 0;
    gap: 12px;
  }
}


</style>